import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;

public class Updater implements Runnable{
	//Data members
	private UDPAdapter _udp = null; //UDP socket adapter
	private static final int MAX_REQUESTS = 20; //Max TCP sessions 
	private int _updateFrequency; 
	private DB _db = null;
	private boolean _flagStop;
	private int _messageMSS;
	private int _picMSS;
	private int _timeout;
	private int _maxIdleTime;
	private ExecutorService _executor = null; //Thread pool for TCP sessions 
	private PictureManager _pictureManager = null; //Picture manager instance
	private String _owner = null; //Owner peer name
	private List<ISocket> _tcpSessions = null; //Sockets of TCP sessions created by handler
	
	/**
	 * Constructor
	 * @param maxIdleTime
	 * @param updateFrequency
	 * @param messageMSS
	 * @param timeout
	 * @param udp
	 * @param db
	 * @param pictureManager
	 * @param picMSS
	 * @param owner
	 */
	public Updater(int maxIdleTime, int updateFrequency, int messageMSS, int timeout, 
			UDPAdapter udp, DB db, PictureManager pictureManager, int picMSS, String owner) {
		_udp = udp; 
		_db = db;
		_updateFrequency = updateFrequency;
		_messageMSS = messageMSS;
		_timeout = timeout;
		_maxIdleTime = maxIdleTime;
		_pictureManager = pictureManager;
		_executor = Executors.newFixedThreadPool(MAX_REQUESTS);
		_picMSS = picMSS;
		_owner = new String(owner);
		_tcpSessions = new LinkedList<ISocket>();
	}
	
	public synchronized void stop() {_flagStop = true;}
	public synchronized boolean isStop() {return _flagStop;}
	
	public void run() {
		start(_owner);
	}
	
	private void sendViaUDP(ISocket socket, Record peer, Date myDBVersion, String me) {
		try {
			_udp.send(new Message(
				String.format("update<$dbversion=%s,$peername=%s>",
						Utils.DateToString(myDBVersion),me).getBytes(),
						InetAddress.getByName(peer.get_ip()), peer.get_udp(), peer.get_peerName()));
			
			
		} catch (UnknownHostException ex) {}
	}
	
	private void sendViaSession(Source source, Record peer, Date myDBVersion, String me) {
		ISocket socket = source.getSocket();
		socket.send(new Message(
				String.format("update<$dbversion=%s,$peername=%s>",
						Utils.DateToString(myDBVersion),me).getBytes(),
						source.getIP(), source.getPort(), peer.get_peerName()));
		
	}
	
	private void start(String myPeerName) {
		String me = myPeerName;
		
		//Scan each online peer and updates it
		while (!isStop()) {
			List<Record> onlines = _db.getOnlinePeers();
			Date myDBVersion = _db.getRecord(me).get_dbVersion();
			
			for (Record peer : onlines) {
				
				if (peer.get_peerName().equals(_owner))
					continue; //Don't update owner peer
					
				switch (peer.get_tries()) {
				case 0: 
					//send update via UDP
					if (peer.get_ip().equals(" ") || peer.get_udp() == -1) {
						//Check if we already have an active session with current peer
						Source source = SessionsManager.getSocket(peer.get_peerName());
						if (source != null)
							sendViaSession(source, peer, myDBVersion, me); //send update via session
					} else
						sendViaUDP(_udp, peer, myDBVersion, me); //send update via UDP
					
					_db.incrementConnectionTries(peer.get_peerName());
					
					break;
				case 1:
					//send update via UDP
					if (peer.get_ip().equals(" ") || peer.get_udp() == -1) {
						//Check if we already have an active session with current peer
						Source source = SessionsManager.getSocket(peer.get_peerName());
						if (source != null)
							sendViaSession(source, peer, myDBVersion, me); //send update via session
					} else
						sendViaUDP(_udp, peer, myDBVersion, me); //send update via UDP
					
					_db.incrementConnectionTries(peer.get_peerName());
					
					break;
				case 2:
					//Check if we already have an active session with current peer
					Source source = SessionsManager.getSocket(peer.get_peerName());
					if (source != null)
						sendViaSession(source, peer, myDBVersion, me); //send update via session
					else
						sendViaUDP(_udp, peer, myDBVersion, me); //send update via UDP
					
					_db.incrementConnectionTries(peer.get_peerName()); //Increment tries
					
					break;
				case 3:
					try {
						//send update via TCP
						if (peer.get_ip().equals(" ") || peer.get_tcp() == -1) {
							//Check if we already have an active session with current peer
							Source tcpSource = SessionsManager.getSocket(peer.get_peerName());
							if (tcpSource != null)
								sendViaSession(tcpSource, peer, myDBVersion, me); //send update via session
						} else {
							TCPAdapter adapt = null;
							Source tcpSource = SessionsManager.getSocket(peer.get_peerName());
							
							//Check if session manager already contains TCP socket of destination
							if (tcpSource != null && tcpSource.getSocket().type().equals("TCP") && tcpSource.getSocket().isAlive()) {
								adapt = (TCPAdapter)tcpSource.getSocket();
							} else {
								Socket tcp = new Socket(InetAddress.getByName(peer.get_ip()),peer.get_tcp());
								
								String ownerIP = _db.getRecord(_owner).get_ip();
								
								if (ownerIP == null)
									ownerIP = "unknown";
								
								
								adapt = new TCPAdapter(_db.get_OwnerName(), ownerIP, tcp, _timeout, _messageMSS);
								_tcpSessions.add(adapt);
								adapt.initialize();
								_executor.execute(new Handler(adapt, _db, _pictureManager, _maxIdleTime, _picMSS));
								SessionsManager.setSocket(peer.get_peerName(), new Source(adapt, tcp.getInetAddress(), tcp.getPort()));
							}
							
							adapt.send(new Message(
									String.format("update<$dbversion=%s,$peername=%s>",
											Utils.DateToString(myDBVersion),me).getBytes(),
											InetAddress.getByName(peer.get_ip()), peer.get_tcp(), peer.get_peerName()));
						
							_db.setPeerOnline(peer.get_peerName());
						}
						
						
					} catch (IOException ex) {
						_db.incrementConnectionTries(peer.get_peerName());
					}
					
					break;
				case 4:
					//Both TCP and UDP failed
					
					SessionsManager.removePeer(peer.get_peerName()); //Delete its session
					_db.setPeerOffline(peer.get_peerName()); //set peer status to offline
					
					Proj350.logger.logError("Peer not available: " + peer.get_peerName());
					
					break;
				}
			}
			
			try {Thread.sleep(_updateFrequency);}
			catch(InterruptedException ex) {} 
		}
		
		_executor.shutdown();
		for (ISocket socket : _tcpSessions)
			socket.close();
	}
}
